#[allow(non_upper_case_globals)]
extern crate gl;
extern crate glfw;

use self::gl::types::*;
use self::glfw::{Action, Context, Key};

use std::ffi::CString;
use std::mem;
use std::os::raw::c_void;
use std::ptr;
use std::str;
use std::sync::mpsc::Receiver;

const SCR_WIDTH: u32 = 800;
const SCR_HEIGHT: u32 = 600;

const VERTEX_SHADER_SOURCE: &str = r#"
    #version 330 core
    layout (location = 0) in vec3 aPos;
    void main() {
        gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
    }
"#;

const FRAGMENT_SHADER_SOURCE: &str = r#"
    #version 330 core
    out vec4 FragColor;
    void main() {
        FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
    }
"#;

const FRAGMENT_SHADER_SOURCE_2: &str = r#"
    #version 330 core
    out vec4 FragColor;
    void main() {
        FragColor = vec4(1.0f, 1.0f, 0.0f, 1.0f);
    }
"#;

#[allow(non_snake_case)]
pub fn main_1_2_5() {
    let mut glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap();
    glfw.window_hint(glfw::WindowHint::ContextVersion(3, 3));
    glfw.window_hint(glfw::WindowHint::OpenGlProfile(glfw::OpenGlProfileHint::Core));
    #[cfg(target_os = "macos")]
    glfw.window_hint(glfw::WindowHint::OpenGlForwardCompat(true));
    let (mut window, events) = glfw
        .create_window(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", glfw::WindowMode::Windowed)
        .expect("Failed to create GLFW window");
    window.make_current();
    window.set_key_polling(true);
    window.set_framebuffer_size_polling(true);

    gl::load_with(|symbol| window.get_proc_address(symbol) as *const _);

    let (shaderProgram, shaderProgram_yellow, mut VBOs, mut VAOs) = unsafe {
        let vertexShader = gl::CreateShader(gl::VERTEX_SHADER);
        let fragmentShader = gl::CreateShader(gl::FRAGMENT_SHADER);
        let fragmentShader_yellow = gl::CreateShader(gl::FRAGMENT_SHADER);

        let shaderProgram = gl::CreateProgram();
        let shaderProgram_yellow = gl::CreateProgram();

        let c_str_vert = CString::new(VERTEX_SHADER_SOURCE.as_bytes()).unwrap();
        gl::ShaderSource(vertexShader, 1, &c_str_vert.as_ptr(), ptr::null());
        gl::CompileShader(vertexShader);

        let c_str_frag = CString::new(FRAGMENT_SHADER_SOURCE.as_bytes()).unwrap();
        gl::ShaderSource(fragmentShader, 1, &c_str_frag.as_ptr(), ptr::null());
        gl::CompileShader(fragmentShader);

        let c_str_frag_yellow = CString::new(FRAGMENT_SHADER_SOURCE_2.as_bytes()).unwrap();
        gl::ShaderSource(fragmentShader_yellow, 1, &c_str_frag_yellow.as_ptr(), ptr::null());
        gl::CompileShader(fragmentShader_yellow);

        gl::AttachShader(shaderProgram, vertexShader);
        gl::AttachShader(shaderProgram, fragmentShader);
        gl::LinkProgram(shaderProgram);

        gl::AttachShader(shaderProgram_yellow, vertexShader);
        gl::AttachShader(shaderProgram_yellow, fragmentShader_yellow);
        gl::LinkProgram(shaderProgram_yellow);

        // top right bottom left

        #[cfg_attr(rustfmt, rustfmt_skip)]
        let first_triangle: [f32; 9] = [
            -0.9, -0.5, 0.,
            -0.0, -0.5, 0.,
            -0.45, 0.5, 0.
        ];

        #[cfg_attr(rustfmt, rustfmt_skip)]
        let second_triangle: [f32; 9] = [
            0.0, -0.5, 0.,
            0.9, -0.5, 0.,
            0.45, 0.5, 0.
        ];

        let (mut VBOs, mut VAOs) = ([0, 0], [0, 0]);

        gl::GenVertexArrays(2, VAOs.as_mut_ptr());
        gl::GenBuffers(2, VBOs.as_mut_ptr());

        // first_triangle
        gl::BindVertexArray(VAOs[0]);
        gl::BindBuffer(gl::ARRAY_BUFFER, VBOs[0]);
        gl::BufferData(
            gl::ARRAY_BUFFER,
            (first_triangle.len() * mem::size_of::<GLfloat>()) as GLsizeiptr,
            &first_triangle[0] as *const f32 as *const c_void,
            gl::STATIC_DRAW,
        );
        gl::VertexAttribPointer(
            0,
            3,
            gl::FLOAT,
            gl::FALSE,
            3 * mem::size_of::<GLfloat>() as GLsizei,
            ptr::null(),
        );
        gl::EnableVertexAttribArray(0);

        // second_triangle
        gl::BindVertexArray(VAOs[1]);
        gl::BindBuffer(gl::ARRAY_BUFFER, VBOs[1]);
        gl::BufferData(
            gl::ARRAY_BUFFER,
            (second_triangle.len() * mem::size_of::<GLfloat>()) as GLsizeiptr,
            &second_triangle[0] as *const f32 as *const c_void,
            gl::STATIC_DRAW,
        );
        gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, 0, ptr::null());
        gl::EnableVertexAttribArray(0);

        // wire frame mode
        // gl::PolygonMode(gl::FRONT_AND_BACK, gl::LINE);

        (shaderProgram, shaderProgram_yellow, VBOs, VAOs)
    };

    while !window.should_close() {
        process_events(&mut window, &events);

        unsafe {
            gl::ClearColor(0.2, 0.3, 0.3, 1.0);
            gl::Clear(gl::COLOR_BUFFER_BIT);

            gl::UseProgram(shaderProgram);
            gl::BindVertexArray(VAOs[0]);
            gl::DrawArrays(gl::TRIANGLES, 0, 3);

            gl::UseProgram(shaderProgram_yellow);
            gl::BindVertexArray(VAOs[1]);
            gl::DrawArrays(gl::TRIANGLES, 0, 3);
        }
        window.swap_buffers();
        glfw.poll_events();
    }
    unsafe {
        gl::DeleteVertexArrays(2, VAOs.as_mut_ptr());
        gl::DeleteBuffers(2, VBOs.as_mut_ptr());
    }
}

fn process_events(window: &mut glfw::Window, events: &Receiver<(f64, glfw::WindowEvent)>) {
    for (_, event) in glfw::flush_messages(events) {
        match event {
            glfw::WindowEvent::FramebufferSize(width, height) => unsafe { gl::Viewport(0, 0, width, height) },
            glfw::WindowEvent::Key(Key::Escape, _, Action::Press, _) => window.set_should_close(true),
            _ => {}
        }
    }
}
